#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<thread>
#include<functional>
#include"ConnectionPool.h"

ConnectionPool::ConnectionPool()
{
	//1.加载配置文件
	if (!LoadConfigFile())
	{
		return;
	}

	//2.创建个数为initSize个连接
	for (int i = 0; i < _initSize; i++)
	{
		//由于此时是系统启动，连接池启动的时候,此时还没有其它线程操作该队列,所以不需要加锁保证该队列的线程安全
		CreateNewConnect();
	}
	LOG(NORMAL, "初始连接量创建成功");

	//3.创建一个专门负责生产连接的线程
	thread produceThread(&ConnectionPool::produceConnectionTask, this);//类的非静态成员函数的第一个参数为this指针
	produceThread.detach();

	//4.创建一个专门负责清理空闲连接的线程
	thread scanThread(&ConnectionPool::scanConnectionTask, this); 
	scanThread.detach();

	LOG(NORMAL,"数据库连接池启动成功");
}


ConnectionPool::~ConnectionPool()
{
	while (!_connectionQueue.empty())
	{
		Connection* front = _connectionQueue.front();
		_connectionQueue.pop();
		delete front;//调用Connection类的析构函数 =>释放连接资源
		front = nullptr;
	}
}

ConnectionPool* ConnectionPool::GetInstance() //基于局部静态变量的懒汉模式
{
	static ConnectionPool inst;
	return &inst;
}

bool ConnectionPool::CreateNewConnect() //用于生产连接的函数 => 需要将新连接放入到队列当中=>线程安全
{
	Connection* p = new Connection();
	if (p != nullptr)
	{
		p->connect(_ip, _port, _username, _password, _dbname); //连接数据库
		_connectionQueue.push(p); //将该条连接插入到队列当中
		p->refreshAliveTime();//刷新当前连接的空闲时间
		_connectionCount++;//创建的连接个数++
		return true;
	}
	return false;
}

//检测是否需要创建新连接的独立线程：当连接队列为空的时候就需要生产新连接，前提是：连接个数没有超过最大连接数
void ConnectionPool::produceConnectionTask()
{
	while (1)
	{
		unique_lock<mutex> ul(_mtx);
		while (!_connectionQueue.empty()) //如果连接队列不为空,那么就在条件变量下等待
		{
			_cv.wait(ul);
		}
		//此时连接队列为空,判断是否需要生产新连接
		if (_connectionCount < _maxSize)
		{
			CreateNewConnect();
		}
		_cv.notify_one(); //唤醒消费者（用户）消费该连接
	}
}

//清理超过最大空闲时间连接的独立线程
void ConnectionPool::scanConnectionTask()
{
	while (1)
	{
		this_thread::sleep_for(chrono::seconds(_maxIdleTime));//通过sleep模拟定时效果
		unique_lock<mutex> ul(_mtx);
		while (_connectionCount > _initSize) //当前连接数比起始连接数多 才需要进行清理
		{
			Connection* front = _connectionQueue.front();//先拿出队头的连接
			//最大超时时间：_maxIdleTime的单位是s  而getAliveTime函数返回的单位是ms
			if (front->getAliveTime() >= (_maxIdleTime * 1000)) //当前队头的连接的空闲时间大于最大空闲时间,需要被清理
			{
				_connectionQueue.pop();
				delete front; //会调用Connection对象的析构函数 => 释放该连接
				front = nullptr;
				_connectionCount--; //当前连接数--
			}
			else //因为队列是先进先出, 队头的连接的空闲时间是最大的,如果队头的连接都不需要被清理,后续的连接也不需要被清理
			{
				break;
			}
		}
	}
}

shared_ptr<Connection> ConnectionPool::GetConnection()
{
	unique_lock<mutex> ul(_mtx);
	while (_connectionQueue.empty()) //如果连接队列当中没有连接,那么需要进行等待
	{ 
		//在条件变量下等待最大超时时间：case1：超时醒来 case2：有连接了被唤醒
		if (cv_status::timeout == _cv.wait_for(ul, chrono::milliseconds(_connectionTimeout)))
		{
			if (_connectionQueue.empty())//此时是超时醒来,如果连接队列仍未空,那么获取连接失败
			{
				LOG(ERROR, "等待最大超时时间(s):" + to_string(_connectionTimeout) + "秒后，连接队列仍为空");
				return nullptr;
			}
		}
		//此时被唤醒,但是连接队列仍为空，此时又会重新等待
	}

	//拿出队头的连接,定制该连接的管理方式,当用户使用完成之后,将该连接回收到连接队列当中
	Connection* front = _connectionQueue.front();
	_connectionQueue.pop();

	//注意:不需要对当前连接数进行++/-- 只有当新建连接和销毁连接才需要对该变量进行操作
	auto lambda = [&](Connection* p) {
		unique_lock<mutex> ul(_mtx);//由于要操作队列,所以要加锁！！
		_connectionQueue.push(p);
		p->refreshAliveTime();//刷新当前连接的空闲时间
	};

	shared_ptr<Connection> sp(front, lambda);

	_cv.notify_one();//唤醒生产者检查连接队列,是否需要生产连接
	return sp;
}


bool ConnectionPool::LoadConfigFile() //加载配置文件,读取配置项,初始化成员变量
{
	FILE* pf = fopen("./mysql.conf", "r");
	if (pf == nullptr)
	{
		LOG(ERROR, "MySQL配置文件打开失败");
		return false;
	}

	LOG(ERROR, "MySQL配置文件打开成功");
	while (!feof(pf)) //没有读取到文件结尾就继续读
	{
		char buf[1024] = { 0 };
		fgets(buf, sizeof(buf) - 1, pf); //一次读取一行配置信息 => port=3306\n
		buf[strlen(buf) - 1] = '\0';//将最后的换行符替换成\0

		string ret = buf;
		//以等号(=)区分每一个配置项的key和value
		size_t index = ret.find('=');
		if (index == std::string::npos) //说明当前配置项无效=>可能是注释
		{
			continue;
		}

		//index位置为空格   [0,index) => key  [index+1,...] =>value
		string key = ret.substr(0, index);
		string value = ret.substr(index + 1);
		
		if (key == "ip")
		{
			_ip = value;
		}
		else if (key == "port")
		{
			_port = atoi(value.c_str());
		}
		else if (key == "username")
		{
			_username = value;
		}
		else if (key == "password")
		{
			_password = value;
		}
		else if (key == "dbname")
		{
			_dbname = value;
		}
		else if (key == "initSize")
		{
			_initSize = atoi(value.c_str());
		}
		else if (key == "maxSize")
		{
			_maxSize = atoi(value.c_str());
		}
		else if (key == "maxIdleTime")
		{
			_maxIdleTime = atoi(value.c_str());
		}
		else if (key == "connectionTimeout")
		{
			_connectionTimeout = atoi(value.c_str());
		}
	}

	LOG(NORMAL, "MySQL配置项读取完毕");
	return true;
}
